-- ======================================================================
-- Hell Lua Bus
-- 
-- Copyright 2012-2013Hell-Prototypes
--
-- http://www.hellprototypes.com/
--
-- This is free software, licensed under the terms of the GNU General
-- Public License as published by the Free Software Foundation.
-- ======================================================================
hlb = require('libhlb')
printf = require "mod_print"
iHEX   = require "mod_iHEX"

--local pgm_hex_file = "atxmega32a4u_104.hex"
local pgm_hex_file = "bootloader.hex"
local g_error = false

function device_open()
	local ret = hlb.usb_open()
	if ret < 0 then
		print("Fail to open usb device")
		return false
	end
	
	return true
end

function device_close()
	local ret = hlb.usb_close()
	if ret < 0 then
		print("Fail to close usb device")
		return false
	end
	
	return true
end
-- -----------------------------------------------
function PDI_CMD_LDS(AddressSize, DataSize)
    return 0x00 + (  AddressSize * 4 ) + DataSize
end
-- -----------------------------------------------
function PDI_CMD_LD(PointerAccess, DataSize)
    return 0x20 + (PointerAccess * 4) + DataSize
end
-- -----------------------------------------------
function PDI_CMD_STS(AddressSize, DataSize)
	local cmd = 0x40 + (  AddressSize * 4) + DataSize
	--print(string.format("PDI_CMD_STS : 0x%04x", cmd))
	return cmd
    --return 0x40 + (  AddressSize * 4) + DataSize
end
-- -----------------------------------------------
function PDI_CMD_ST(PointerAccess, DataSize)
    return 0x60 + (PointerAccess * 4) + DataSize
end
-- -----------------------------------------------
function PDI_CMD_LDCS(PDIReg)
    return 0x80 + PDIReg
end
-- -----------------------------------------------
function PDI_CMD_REPEAT(DataSize)
    return 0xA0 + DataSize
end
-- -----------------------------------------------
function PDI_CMD_STCS(PDIReg)
    return 0xC0 + PDIReg
end
-- -----------------------------------------------
PDI_CMD_KEY = 0xE0
-- -----------------------------------------------
PDI_REG_STATUS = 0
PDI_REG_RESET  = 1
PDI_REG_CTRL   = 2

PDI_RESET_KEY  = 0x59
PDI_NVMENABLE_KEY = {0x12, 0x89, 0xAB, 0x45, 0xCD, 0xD8, 0x88, 0xFF}

PDI_DATASIZE_1BYTE     =    0
PDI_DATASIZE_2BYTES    =    1
PDI_DATASIZE_3BYTES    =    2
PDI_DATASIZE_4BYTES    =    3

PDI_POINTER_INDIRECT   =    0
PDI_POINTER_INDIRECT_PI=    1
PDI_POINTER_DIRECT     =    2
-- -----------------------------------------------
XPROG_Param_NVMBase    = 0x010001C0

XMEGA_NVM_REG_CMD      = 0x0A
XMEGA_NVM_REG_CTRLA    = 0x0B
XMEGA_NVM_BIT_CTRLA_CMDEX = 1

XMEGA_NVM_CMD_CHIPERASE   =  0x40
XMEGA_NVM_CMD_WRITEFUSE   =  0x4C
XMEGA_NVM_CMD_WRITELOCK   =  0x08

PDI_MAP_FUSE_BASE = 0x08F0020
-- -----------------------------------------------
function XPROGTarget_SendByte(send)
	if send == nil then
		print("XPROGTarget_SendByte: Error nil value")
		g_error = true
		return
	end
    hlb.pdi_write(send);
end
-- -----------------------------------------------
function XPROGTarget_ReceiveByte()
    return hlb.pdi_read()
end
-- -----------------------------------------------
function XMEGANVM_SendAddress(Address)
    local B3 = bit32.band(bit32.rshift(Address, 24), 0xFF)
	local B2 = bit32.band(bit32.rshift(Address, 16), 0xFF)
	local B1 = bit32.band(bit32.rshift(Address, 8),  0xFF)
	local B0 = bit32.band(Address, 0xFF)

	XPROGTarget_SendByte(B0)
	XPROGTarget_SendByte(B1)
	XPROGTarget_SendByte(B2)
	XPROGTarget_SendByte(B3)
end
-- -----------------------------------------------
function ReadMemory_1Byte(ReadAddress)
    	XPROGTarget_SendByte(PDI_CMD_LDS(PDI_DATASIZE_4BYTES, PDI_DATASIZE_1BYTE))
		XMEGANVM_SendAddress(ReadAddress)
		return XPROGTarget_ReceiveByte()
end
-- -----------------------------------------------
function ReadControlerStatus()
    XPROGTarget_SendByte(PDI_CMD_LDCS(PDI_REG_STATUS))
	return XPROGTarget_ReceiveByte()
end
-- -----------------------------------------------
function XMEGANVM_EnablePDI()
	hlb.pdi_ctrl(0)
    hlb.bit_oe(1, 1)
    hlb.bit_oe(2, 1)
    hlb.set_bit( 2 )
    hlb.clr_bit( 1 )

    hlb.msdelay(10)

	hlb.pdi_ctrl(1)

	XPROGTarget_SendByte(PDI_CMD_STCS(PDI_REG_RESET))
	XPROGTarget_SendByte(PDI_RESET_KEY)

	XPROGTarget_SendByte(PDI_CMD_KEY)
	for i = 1, 8 do
	   XPROGTarget_SendByte(PDI_NVMENABLE_KEY[9-i])
    end
end
-- -----------------------------------------------
function XMEGANVM_DisablePDI()
	XPROGTarget_SendByte(PDI_CMD_STCS(PDI_REG_RESET))
	XPROGTarget_SendByte(0x00)

	hlb.pdi_ctrl(0)
end
-- -----------------------------------------------
function XMEGANVM_SendNVMRegAddress(Register)
	local Address = XPROG_Param_NVMBase + Register
	XMEGANVM_SendAddress(Address)
end
-- -----------------------------------------------
function XMEGANVM_ChipErase()
	-- Send the memory erase command to the target */
	XPROGTarget_SendByte(PDI_CMD_STS(PDI_DATASIZE_4BYTES, PDI_DATASIZE_1BYTE))
	XMEGANVM_SendNVMRegAddress(XMEGA_NVM_REG_CMD)
	XPROGTarget_SendByte(XMEGA_NVM_CMD_CHIPERASE)

	-- Set CMDEX bit in NVM CTRLA register to start the erase sequence
	XPROGTarget_SendByte(PDI_CMD_STS(PDI_DATASIZE_4BYTES, PDI_DATASIZE_1BYTE));
	XMEGANVM_SendNVMRegAddress(XMEGA_NVM_REG_CTRLA);
	XPROGTarget_SendByte(XMEGA_NVM_BIT_CTRLA_CMDEX);
end
-- -----------------------------------------------
function XMEGANVM_WriteByteMemory(WriteCommand, WriteAddress, Byte)
	-- Send the memory write command to the target
	XPROGTarget_SendByte(PDI_CMD_STS(PDI_DATASIZE_4BYTES, PDI_DATASIZE_1BYTE));
	XMEGANVM_SendNVMRegAddress(XMEGA_NVM_REG_CMD);
	XPROGTarget_SendByte(WriteCommand);

	-- Send new memory byte to the memory of the target
	XPROGTarget_SendByte(PDI_CMD_STS(PDI_DATASIZE_4BYTES, PDI_DATASIZE_1BYTE));
	XMEGANVM_SendAddress(WriteAddress);
	XPROGTarget_SendByte(Byte);
	
	hlb.msdelay(10)
end
-- -----------------------------------------------
XMEGA_NVM_CMD_LOADFLASHPAGEBUFF  =   0x23
XMEGA_NVM_CMD_WRITEBOOTSECPAGE   =   0x2C
function XMEGANVM_BootWriteOnePage(WriteAddress, WriteBuffer, WriteSize)
	-- Send the memory buffer write command to the target
	XPROGTarget_SendByte(PDI_CMD_STS(PDI_DATASIZE_4BYTES, PDI_DATASIZE_1BYTE));
	XMEGANVM_SendNVMRegAddress(XMEGA_NVM_REG_CMD);
	XPROGTarget_SendByte(XMEGA_NVM_CMD_LOADFLASHPAGEBUFF);

	-- Load the PDI pointer register with the start address we want to write to
	XPROGTarget_SendByte(PDI_CMD_ST(PDI_POINTER_DIRECT, PDI_DATASIZE_4BYTES));
	XMEGANVM_SendAddress(WriteAddress);

	-- Send the REPEAT command with the specified number of bytes to write
	XPROGTarget_SendByte(PDI_CMD_REPEAT(PDI_DATASIZE_1BYTE));
	XPROGTarget_SendByte(WriteSize - 1);

	-- Send a ST command with indirect access and post-increment to write the bytes
	XPROGTarget_SendByte(PDI_CMD_ST(PDI_POINTER_INDIRECT_PI, PDI_DATASIZE_1BYTE));
	for i=1, WriteSize do
		XPROGTarget_SendByte(WriteBuffer[i]);
		io.write(".")
	end

	-- Write page buffer

	-- Send the memory write command to the target */
	XPROGTarget_SendByte(PDI_CMD_STS(PDI_DATASIZE_4BYTES, PDI_DATASIZE_1BYTE));
	XMEGANVM_SendNVMRegAddress(XMEGA_NVM_REG_CMD);
	XPROGTarget_SendByte(XMEGA_NVM_CMD_WRITEBOOTSECPAGE);

	-- Send the address of the first page location to write the memory page */
	XPROGTarget_SendByte(PDI_CMD_STS(PDI_DATASIZE_4BYTES, PDI_DATASIZE_1BYTE));
	XMEGANVM_SendAddress(WriteAddress);
	XPROGTarget_SendByte(0x00);

	return true;
end
-- -----------------------------------------------
PDI_MAP_FLASH_BASE = 0x0800000
PDI_MAP_SIZE_APPL  = 0x8000 --32K
PDI_MAP_BOOT_BASE  = PDI_MAP_FLASH_BASE + PDI_MAP_SIZE_APPL

FLASH_PAGE_SIZE    = 256
function pdi_boot_program(start_addr, pgm_raw_data)
	local index = 1
	local wr_data={}
	local percent = 0
	local last_percent = -1
	while index < #pgm_raw_data do
		for i=0, FLASH_PAGE_SIZE do --page size is 256B
			if i+index <= #pgm_raw_data then
				wr_data[i+1] = pgm_raw_data[i+index]
			else
				wr_data[i+1] = 0xFF;
			end
		end

		print(string.format("Write At Address : 0x%04x", start_addr + index - 1))
		XMEGANVM_BootWriteOnePage(PDI_MAP_BOOT_BASE + index - 1, wr_data, FLASH_PAGE_SIZE)
		if g_error == true then
			return
		end

		percent = math.floor((100*index)/#pgm_raw_data)
		if percent ~= last_percent then
			last_percent = percent
			print(string.format("\r\n%%%02d ", percent))
		end
		index = index + FLASH_PAGE_SIZE
		hlb.msdelay(20)
	end
	print("100%")
end
-- -----------------------------------------------
f=io.open(pgm_hex_file,"r")
if f~=nil then
	io.close(f)
else
	print("Error: Program file not exist, file name: " .. pgm_hex_file)
	return
end

local pgm_start_addr
local pgm_raw_data = {}
local pgm_raw_data_temp
pgm_start_addr, pgm_raw_data_temp = iHEX.Read_File(pgm_hex_file)
local index = 1
for i=pgm_start_addr+1, #pgm_raw_data_temp do
	pgm_raw_data[index] = pgm_raw_data_temp[i]
	index = index + 1
end
pgm_raw_data_temp = nil
if type(pgm_raw_data) == "table" then
	printf.table(pgm_raw_data, true)
	print(string.format("FW start address = 0x%04x", pgm_start_addr))
else
	print("Error: fail to read hex file.")
	return
end
-- -----------------------------------------------
function exit_app()
	XMEGANVM_DisablePDI()
	device_close()
end
-- -----------------------------------------------
-- Main
---[[
device_open()

XMEGANVM_EnablePDI()
-- -----------------------------------------------

ret = ReadControlerStatus()
if ret ~= 2 then
	print("Fail to enable PDI, Status = " .. ret)
	exit_app()
	return
end

hlb.msdelay(10)

--Chip Erase
print("Chip Erase ...")
XMEGANVM_ChipErase() --50ms
if g_error == true then
	exit_app()
	return
end
hlb.msdelay(500)

---[[
--Write Boot Flash
print("Write Boot Flash ...")
pdi_boot_program(pgm_start_addr, pgm_raw_data)
if g_error == true then
	exit_app()
	return
end
print("DONE ...")

--Write Fuse
print("Write Fuse Bytes...")
--XMEGANVM_WriteByteMemory(XMEGA_NVM_CMD_WRITEFUSE, PDI_MAP_FUSE_BASE + 0, 0xff)
XMEGANVM_WriteByteMemory(XMEGA_NVM_CMD_WRITEFUSE, PDI_MAP_FUSE_BASE + 1, 0x00)
XMEGANVM_WriteByteMemory(XMEGA_NVM_CMD_WRITEFUSE, PDI_MAP_FUSE_BASE + 2, 0xbf) -- boot to bootloader
XMEGANVM_WriteByteMemory(XMEGA_NVM_CMD_WRITEFUSE, PDI_MAP_FUSE_BASE + 4, 0xee) -- disable reset pin
XMEGANVM_WriteByteMemory(XMEGA_NVM_CMD_WRITEFUSE, PDI_MAP_FUSE_BASE + 5, 0xff)
if g_error == true then
	exit_app()
	return
end
--Write Lockbits
print("Write Lockbits ...")
XMEGANVM_WriteByteMemory(XMEGA_NVM_CMD_WRITELOCK, PDI_MAP_FUSE_BASE + 7, 0x3C) --LOCKBITS
--]]

ret = ReadControlerStatus()
print("Status = " .. ret)

-- -----------------------------------------------
exit_app()
if ret ~= 2 then
print("*** Warning: Program download FAIL ***")
else
print("ALL DONE ...")
end
--]]
